/*******************************************************************************
 * Copyright (c) 2016 Rogue Wave Software, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Rogue Wave Software, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.plugin.zdb.server.connection;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Set;

import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.AddBreakpointResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.AddFilesResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.AssignValueResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.DeleteAllBreakpointsResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.DeleteBreakpointResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.EvalResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.GetCWDResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.GetCallStackResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.GetStackVariableValueResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.GetVariableValueResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.GoResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.IDbgEngineResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.PauseDebuggerResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.SetProtocolResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.StartResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.StepIntoResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.StepOutResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.StepOverResponse;
import org.eclipse.che.plugin.zdb.server.utils.ZendDbgConnectionUtils;

/**
 * Zend debug client messages container.
 *
 * @author Bartlomiej Laczkowski
 */
public class ZendDbgClientMessages {

    // Notification types
    public static final int NOTIFICATION_CONTINUE_PROCESS_FILE = 2010;
    public static final int NOTIFICATION_CLOSE_SESSION = 3;
    // Request types
    public static final int REQUEST_START = 1;
    public static final int REQUEST_PAUSE_DEBUGGER = 2;
    public static final int REQUEST_STEP_INTO = 11;
    public static final int REQUEST_STEP_OVER = 12;
    public static final int REQUEST_STEP_OUT = 13;
    public static final int REQUEST_GO = 14;
    public static final int REQUEST_ADD_BREAKPOINT = 21;
    public static final int REQUEST_DELETE_BREAKPOINT = 22;
    public static final int REQUEST_DELETE_ALL_BREAKPOINTS = 23;
    public static final int REQUEST_EVAL = 31;
    public static final int REQUEST_GET_VARIABLE_VALUE = 32;
    public static final int REQUEST_ASSIGN_VALUE = 33;
    public static final int REQUEST_GET_CALL_STACK = 34;
    public static final int REQUEST_GET_STACK_VARIABLE_VALUE = 35;
    public static final int REQUEST_GET_CWD = 36;
    public static final int REQUEST_ADD_FILES = 38;
    public static final int REQUEST_SET_PROTOCOL = 10000;
    // Response types
    public static final int RESPONSE_GET_LOCAL_FILE_CONTENT = 11001;

    public interface IDbgClientMessage extends IDbgMessage {

        /**
         * Serializes this debug message to an output stream
         *
         * @param out
         *            output stream this message is going to be written to
         */
        public void serialize(DataOutputStream out) throws IOException;

    }

    public interface IDbgClientNotification extends IDbgClientMessage {
    }

    public interface IDbgClientRequest<T extends IDbgEngineResponse> extends IDbgClientMessage {

        /**
         * Set the client request id.
         */
        public void setID(int id);

        /**
         * Return the client request id.
         */
        public int getID();

    }

    public interface IDbgClientResponse extends IDbgClientMessage {

        /**
         * Return the engine response id.
         */
        public int getID();

        /**
         * Return the engine response status.
         */
        public int getStatus();

    }

    private static abstract class AbstractClientRequest<T extends IDbgEngineResponse> extends AbstractDbgMessage
            implements IDbgClientRequest<T> {

        private int id;

        @Override
        public void setID(int id) {
            this.id = id;
        }

        @Override
        public int getID() {
            return this.id;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            out.writeShort(getType());
            out.writeInt(getID());
        }

    }

    private static abstract class AbstractClientResponse extends AbstractDbgMessage implements IDbgClientResponse {

        protected int id;
        protected int status;

        public AbstractClientResponse(int id) {
            this.id = id;
        }

        @Override
        public int getID() {
            return this.id;
        }

        @Override
        public int getStatus() {
            return status;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            out.writeShort(getType());
            out.writeInt(getID());
            out.writeInt(getStatus());
        }

    }

    private ZendDbgClientMessages() {
    }

    // Client notifications

    public static class ContinueProcessFileNotification extends AbstractDbgMessage implements IDbgClientNotification {

        @Override
        public int getType() {
            return NOTIFICATION_CONTINUE_PROCESS_FILE;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            out.writeShort(getType());
        }
    }

    public static class CloseSessionNotification extends AbstractDbgMessage implements IDbgClientNotification {

        @Override
        public int getType() {
            return NOTIFICATION_CLOSE_SESSION;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            out.writeShort(getType());
        }
    }

    // Client requests

    public static class AddBreakpointRequest extends AbstractClientRequest<AddBreakpointResponse> {

        private int kind;
        private int lifeTime;
        private int lineNumber;
        private String fileName;

        public AddBreakpointRequest(int kind, int lifeTime, int line, String fileName) {
            this.kind = kind;
            this.lifeTime = lifeTime;
            this.lineNumber = line;
            this.fileName = fileName;
        }

        @Override
        public int getType() {
            return REQUEST_ADD_BREAKPOINT;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            super.serialize(out);
            out.writeShort(kind);
            out.writeShort(lifeTime);
            ZendDbgConnectionUtils.writeString(out, fileName);
            out.writeInt(lineNumber);
        }
    }

    public static class AddFilesRequest extends AbstractClientRequest<AddFilesResponse> {

        private Set<String> paths;

        public AddFilesRequest(Set<String> paths) {
            this.paths = paths;
        }

        @Override
        public int getType() {
            return REQUEST_ADD_FILES;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            super.serialize(out);
            out.writeInt(paths.size());
            for (String path : paths) {
                ZendDbgConnectionUtils.writeString(out, path);
            }
        }
    }

    public static class AssignValueRequest extends AbstractClientRequest<AssignValueResponse> {

        private String var;
        private String value;
        private int depth;
        private List<String> path;

        public AssignValueRequest(String var, String value, int depth, List<String> path) {
            this.var = var;
            this.value = value;
            this.depth = depth;
            this.path = path;
        }

        @Override
        public int getType() {
            return REQUEST_ASSIGN_VALUE;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            super.serialize(out);
            ZendDbgConnectionUtils.writeEncodedString(out, var, getTransferEncoding());
            ZendDbgConnectionUtils.writeEncodedString(out, value, getTransferEncoding());
            out.writeInt(depth);
            out.writeInt(path.size());
            for (String p : path) {
                ZendDbgConnectionUtils.writeString(out, p);
            }
        }
    }

    public static class DeleteAllBreakpointsRequest extends AbstractClientRequest<DeleteAllBreakpointsResponse> {

        @Override
        public int getType() {
            return REQUEST_DELETE_ALL_BREAKPOINTS;
        }
    }

    public static class DeleteBreakpointRequest extends AbstractClientRequest<DeleteBreakpointResponse> {

        private int breakpointId;

        public DeleteBreakpointRequest(int breakpointId) {
            this.breakpointId = breakpointId;
        }

        @Override
        public int getType() {
            return REQUEST_DELETE_BREAKPOINT;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            super.serialize(out);
            out.writeInt(breakpointId);
        }
    }

    public static class EvalRequest extends AbstractClientRequest<EvalResponse> {

        private String command;

        public EvalRequest(String command) {
            this.command = command;
        }

        @Override
        public int getType() {
            return REQUEST_EVAL;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            super.serialize(out);
            ZendDbgConnectionUtils.writeEncodedString(out, command, getTransferEncoding());
        }
    }

    public static class GetCallStackRequest extends AbstractClientRequest<GetCallStackResponse> {

        @Override
        public int getType() {
            return REQUEST_GET_CALL_STACK;
        }
    }

    public static class GetCWDRequest extends AbstractClientRequest<GetCWDResponse> {

        @Override
        public int getType() {
            return REQUEST_GET_CWD;
        }
    }

    public static class GetStackVariableValueRequest extends AbstractClientRequest<GetStackVariableValueResponse> {

        private String var;
        private int depth;
        private int layerDepth;
        private List<String> path;

        public GetStackVariableValueRequest(String var, int depth, int layerDepth, List<String> path) {
            this.var = var;
            this.depth = depth;
            this.layerDepth = layerDepth;
            this.path = path;
        }

        @Override
        public int getType() {
            return REQUEST_GET_STACK_VARIABLE_VALUE;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            super.serialize(out);
            out.writeInt(layerDepth);
            ZendDbgConnectionUtils.writeEncodedString(out, var, getTransferEncoding());
            out.writeInt(depth);
            out.writeInt(path.size());
            for (String p : path) {
                ZendDbgConnectionUtils.writeString(out, p);
            }
        }
    }

    public static class GetVariableValueRequest extends AbstractClientRequest<GetVariableValueResponse> {

        private String var;
        private int depth;
        private List<String> path;

        public GetVariableValueRequest(String var, int depth, List<String> path) {
            this.var = var;
            this.depth = depth;
            this.path = path;
        }

        @Override
        public int getType() {
            return REQUEST_GET_VARIABLE_VALUE;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            super.serialize(out);
            ZendDbgConnectionUtils.writeEncodedString(out, var, getTransferEncoding());
            out.writeInt(depth);
            out.writeInt(path.size());
            for (String p : path) {
                ZendDbgConnectionUtils.writeString(out, p);
            }
        }
    }

    public static class GoRequest extends AbstractClientRequest<GoResponse> {

        @Override
        public int getType() {
            return REQUEST_GO;
        }
    }

    public static class PauseDebuggerRequest extends AbstractClientRequest<PauseDebuggerResponse> {

        @Override
        public int getType() {
            return REQUEST_PAUSE_DEBUGGER;
        }
    }

    public static class SetProtocolRequest extends AbstractClientRequest<SetProtocolResponse> {

        private int protocolID;

        public SetProtocolRequest(int protocolID) {
            this.protocolID = protocolID;
        }

        @Override
        public int getType() {
            return REQUEST_SET_PROTOCOL;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            super.serialize(out);
            out.writeInt(protocolID);
        }
    }

    public static class StartRequest extends AbstractClientRequest<StartResponse> {

        @Override
        public int getType() {
            return REQUEST_START;
        }
    }

    public static class StepIntoRequest extends AbstractClientRequest<StepIntoResponse> {

        @Override
        public int getType() {
            return REQUEST_STEP_INTO;
        }
    }

    public static class StepOutRequest extends AbstractClientRequest<StepOutResponse> {

        @Override
        public int getType() {
            return REQUEST_STEP_OUT;
        }
    }

    public static class StepOverRequest extends AbstractClientRequest<StepOverResponse> {

        @Override
        public int getType() {
            return REQUEST_STEP_OVER;
        }
    }

    // Client responses

    public static class GetLocalFileContentResponse extends AbstractClientResponse {

        public static final int STATUS_FAILURE = -1;
        public static final int STATUS_SUCCESS = 0;
        public static final int STATUS_FILES_IDENTICAL = 302;

        private byte content[] = null;

        public GetLocalFileContentResponse(int id, int status, byte[] content) {
            super(id);
            this.status = status;
            this.content = content;
        }

        @Override
        public void serialize(DataOutputStream out) throws IOException {
            super.serialize(out);
            if (content != null) {
                out.write(content.length);
                out.write(content);
            } else {
                out.writeInt(0);
            }
        }

        @Override
        public int getType() {
            return RESPONSE_GET_LOCAL_FILE_CONTENT;
        }

    }

}
